package org.hepeng.workx.mybatis.mapper;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.reflect.MethodUtils;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.apache.ibatis.executor.keygen.KeyGenerator;
import org.apache.ibatis.executor.keygen.NoKeyGenerator;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.mapping.StatementType;
import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.session.Configuration;
import org.springframework.util.Assert;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Objects;

import static org.apache.ibatis.mapping.SqlCommandType.*;

/**
 * @author he peng
 */
public abstract class AbstractMappedStatementSupplier implements MappedStatementSupplier {

    protected TableMapMetadata tableMapMetaData;
    protected MapperBuilderAssistant assistant;
    protected Configuration configuration;
    private Boolean initialized;

    public AbstractMappedStatementSupplier() {}

    public AbstractMappedStatementSupplier(TableMapMetadata tableMapMetaData, MapperBuilderAssistant assistant , Configuration configuration) {
        initialize(tableMapMetaData, assistant , configuration);
    }

    @Override
    public void initialize(TableMapMetadata tableMapMetaData, MapperBuilderAssistant assistant, Configuration configuration) {
        Assert.notNull(tableMapMetaData, "tableMapMetaData must not be null");
        Assert.notNull(assistant , "assistant must not be null");
        Assert.notNull(configuration , "configuration must not be null");
        this.tableMapMetaData = tableMapMetaData;
        this.assistant = assistant;
        this.configuration = configuration;
    }


    @Override
    public void supplyAllColumnInsertStatement() {
        supplyStatement(InsertAllColumn.class, statementMethod -> {
            LanguageDriver languageDriver = configuration.getLanguageRegistry().getDefaultDriver();
            AbstractMappedStatementSupplier.this.addMappedStatement(
                    statementMethod.getName() , createAllColumnInsertSQL() ,
                    INSERT , tableMapMetaData.getEntityClass() , null ,
                    statementMethod.getReturnType() , new NoKeyGenerator() , languageDriver);
        });
    }


    @Override
    public void supplySelectiveColumnInsertStatement() {
        supplyStatement(InsertSelectiveColumn.class, statementMethod -> {
            LanguageDriver languageDriver = configuration.getLanguageRegistry().getDefaultDriver();
            AbstractMappedStatementSupplier.this.addMappedStatement(
                    statementMethod.getName() , createSelectiveColumnInsertSQL() ,
                    INSERT , tableMapMetaData.getEntityClass() , null ,
                    statementMethod.getReturnType() , new NoKeyGenerator() , languageDriver);
        });
    }

    @Override
    public void supplyAllColumnUpdateByIdStatement() {
        supplyStatement(UpdateAllColumn.class, statementMethod -> {
            LanguageDriver languageDriver = configuration.getLanguageRegistry().getDefaultDriver();
            AbstractMappedStatementSupplier.this.addMappedStatement(
                    statementMethod.getName() , createAllColumnUpdateByIdSQL() ,
                    UPDATE , tableMapMetaData.getEntityClass() , null ,
                    statementMethod.getReturnType() , new NoKeyGenerator() , languageDriver);
        });
    }

    @Override
    public void supplySelectiveUpdateByIdStatement() {
        supplyStatement(UpdateSelectiveColumn.class, statementMethod -> {
            LanguageDriver languageDriver = configuration.getLanguageRegistry().getDefaultDriver();
            AbstractMappedStatementSupplier.this.addMappedStatement(
                    statementMethod.getName() , createSelectiveColumnUpdateByIdSQL() ,
                    UPDATE , tableMapMetaData.getEntityClass() , null ,
                    statementMethod.getReturnType() , new NoKeyGenerator() , languageDriver);
        });
    }

    @Override
    public void supplyDeleteByIdAtPhysicalStatement() {
        supplyStatement(DeleteAtPhysical.class, statementMethod -> {
            LanguageDriver languageDriver = configuration.getLanguageRegistry().getDefaultDriver();
            AbstractMappedStatementSupplier.this.addMappedStatement(
                    statementMethod.getName() , createDeleteByIdAtPhysicalSQL() ,
                    DELETE , Object.class , null ,
                    statementMethod.getReturnType() , new NoKeyGenerator() , languageDriver);
        });
    }


    @Override
    public void supplyDeleteByIdAtLogicalStatement() {
        supplyStatement(DeleteAtLogic.class, statementMethod -> {
            LanguageDriver languageDriver = configuration.getLanguageRegistry().getDefaultDriver();
            AbstractMappedStatementSupplier.this.addMappedStatement(
                    statementMethod.getName() , createDeleteByIdAtLogicSQL() ,
                    DELETE , Object.class , null ,
                    statementMethod.getReturnType() , new NoKeyGenerator() , languageDriver);
        });
    }


    @Override
    public void supplySelectByIdStatement() {
        supplyStatement(SelectByPrimaryKey.class, statementMethod -> {
            LanguageDriver languageDriver = configuration.getLanguageRegistry().getDefaultDriver();
            AbstractMappedStatementSupplier.this.addMappedStatement(
                    statementMethod.getName() , createSelectByIdSQL() ,
                    SELECT , Object.class , null ,
                    tableMapMetaData.getEntityClass() , new NoKeyGenerator() , languageDriver);
        });
    }

    @Override
    public void supplySelectByDynamicWhereStatement() {
        supplyStatement(SelectByDynamicWhere.class, statementMethod -> {
            LanguageDriver languageDriver = configuration.getLanguageRegistry().getDefaultDriver();
            AbstractMappedStatementSupplier.this.addMappedStatement(
                    statementMethod.getName() , createSelectByDynamicWhereSQL() ,
                    SELECT , tableMapMetaData.getEntityClass() , null ,
                    tableMapMetaData.getEntityClass() , new NoKeyGenerator() , languageDriver);
        });
    }

    protected abstract String createAllColumnInsertSQL();

    protected abstract String createSelectiveColumnInsertSQL();

    protected abstract String createAllColumnUpdateByIdSQL();

    protected abstract String createSelectiveColumnUpdateByIdSQL();

    protected abstract String createDeleteByIdAtPhysicalSQL();

    protected abstract String createDeleteByIdAtLogicSQL();

    protected abstract String createSelectByIdSQL();

    protected abstract String createSelectByDynamicWhereSQL();

    private void addMappedStatement(String id , String sql , SqlCommandType sqlCommandType ,
                                    Class<?> parameterType , String resultMap , Class<?> resultType ,
                                    KeyGenerator keyGenerator , LanguageDriver lang) {
        if (configuration.hasStatement(assistant.applyCurrentNamespace(id, false))) {
            return;
        }

        boolean isSelect = Objects.equals(SELECT , sqlCommandType) ? true : false;
        assistant.addMappedStatement(id , createSqlSource(sql) , StatementType.PREPARED ,
                sqlCommandType , null , null , null ,
                parameterType , resultMap , resultType ,
                null, ! isSelect , isSelect , false , keyGenerator,
                getKeyProperty() , getKeyColumn() , configuration.getDatabaseId() , lang , null);
    }

    private SqlSource createSqlSource(String sql) {
        LanguageDriver languageDriver = configuration.getLanguageRegistry().getDefaultDriver();
        return languageDriver.createSqlSource(configuration, sql, tableMapMetaData.getEntityClass());
    }

    private String getKeyProperty() {
        StringBuilder keyPropertyBuilder = new StringBuilder();
        for (String identityFieldName : tableMapMetaData.getIdentityFieldNames()) {
            keyPropertyBuilder.append(identityFieldName).append(",");
        }
        return keyPropertyBuilder.toString();
    }

    private String getKeyColumn() {
        StringBuilder keyColumnBuilder = new StringBuilder();
        for (String identityColumnName : tableMapMetaData.getIdentityColumnNames()) {
            keyColumnBuilder.append(identityColumnName).append(",");
        }
        return keyColumnBuilder.toString();
    }

    private void supplyStatement(Class<? extends Annotation> annotationClass , AddMappedStatementAction action) {
        List<Method> statementMethods = MethodUtils.getMethodsListWithAnnotation(
                tableMapMetaData.getMapperClass() , annotationClass);
        if (CollectionUtils.isNotEmpty(statementMethods)) {
            for (Method statementMethod : statementMethods) {
                action.addMappedStatement(statementMethod);
            }
        }
    }

    @FunctionalInterface
    private interface AddMappedStatementAction {
        void addMappedStatement(Method statementMethod);
    }
}
